マンガデータの量を見る#
マンガデータを例に、量を見るためのデータ可視化手法を学びましょう:
質的変数の量を見る最も一般的な方法は 棒グラフ を作図することです。 複数の質的変数を扱う場合は、集合棒グラフや積上げ棒グラフも効果的です。 前者は特に絶対値を比較したい場合、後者は特に割合を比較したい場合に便利です。 全体像を俯瞰したい場合は、ヒートマップを選択肢に入れましょう。 ただし、ヒートマップは質的変数の組合せの量を色で表現するため、数値を付記しなければ絶対値の比較が難しいことにご注意ください。
なお、データ可視化手法の選定に関しては、Claus O. Wilke, Fundamentals of Data Visualizationの5.1 Amountsを参考にしました。
有意義なデータ可視化を行うために重要なポイントの一つは、 仮説を立てる ことです[貴裕, 2023]。 ドメイン知識を利用して仮説を立て、可視化によって誤りがないか確認[1]し次の分析に繋げることで、効果的に作業を進めることができます。 本ハンズオンではこの考えに則り、各可視化手法を取り上げる前に可能な限り[2]仮説を設定します[3]。
初期設定#
以降では、マンガ・アニメ・ゲームデータを可視化するための初期設定を行います。 紙幅の都合のため、書籍版と一部構成が異なることにご注意ください。
Import#
必要なライブラリをImportします。
Show code cell content
# warningsモジュールのインポート
import warnings
# データ解析や機械学習のライブラリ使用時の警告を非表示にする目的で警告を無視
# 本書の文脈では、可視化の学習に議論を集中させるために選択した
# ただし、学習以外の場面で、警告を無視する設定は推奨しない
warnings.filterwarnings("ignore")
Show code cell content
# itertoolsモジュールのインポート
# 様々なパターンのループを効率的に実行可能
import itertools
# pathlibモジュールのインポート
# ファイルシステムのパスを扱う
from pathlib import Path
# numpy:数値計算ライブラリのインポート
# npという名前で参照可能
import numpy as np
# pandas:データ解析ライブラリのインポート
# pdという名前で参照可能
import pandas as pd
# plotly.expressのインポート
# インタラクティブなグラフ作成のライブラリ
# pxという名前で参照可能
import plotly.express as px
# plotly.graph_objectsからFigureクラスのインポート
# 型ヒントの利用を主目的とする
from plotly.graph_objects import Figure
Pythonにおける型ヒント
「型ヒント」とは、Pythonのコードにおいて、変数や関数の引数、返り値の型を示すためのヒントを提供するものです。 これにより、コードの可読性が向上したり、型チェッカーなどのツールがエラーを検出しやすくなります。
定数#
本Notebookで用いる定数を定義します。 なお、Pythonにおける定数の扱いについては、こちらを参照ください。
Show code cell content
# マンガデータが保存されているディレクトリのパス
DIR_IN = Path("../../data/cm/input")
# 分析結果の出力先ディレクトリのパス
DIR_OUT = DIR_IN.parent / "output" / Path.cwd().parts[-1] / "amounts"
Show code cell content
# 読み込み対象ファイル
# Comic Episode関連のファイル名
FN_CE = "cm_ce.csv"
Show code cell content
# plotlyの描画設定の定義
# plotlyのグラフ描画用レンダラーの定義
# Jupyter Notebook環境のグラフ表示に適切なものを選択
RENDERER = "plotly_mimetype+notebook"
関数#
本Notebookで用いる関数を定義します。
Show code cell content
def show_fig(fig: Figure) -> None:
"""
所定のレンダラーを用いてplotlyの図を表示
Jupyter Bookなどの環境での正確な表示を目的とする
Parameters
----------
fig : Figure
表示対象のplotly図
Returns
-------
None
"""
# 図の周囲の余白を設定
# t: 上余白
# l: 左余白
# r: 右余白
# b: 下余白
fig.update_layout(margin=dict(t=25, l=25, r=25, b=25))
# 所定のレンダラーで図を表示
fig.show(renderer=RENDERER)
Show code cell content
def add_years_to_df(
df: pd.DataFrame, unit_years: int = 10, col_date: str = "date"
) -> pd.DataFrame:
"""
データフレームにunit_years単位で区切った年数を示す新しい列を追加
Parameters
----------
df : pd.DataFrame
入力データフレーム
unit_years : int, optional
年数を区切る単位、デフォルトは10
col_date : str, optional
日付を含むカラム名、デフォルトは "date"
Returns
-------
pd.DataFrame
新しい列が追加されたデータフレーム
"""
# 入力データフレームをコピー
df_new = df.copy()
# unit_years単位で年数を区切り、新しい列として追加
df_new["years"] = (
pd.to_datetime(df_new[col_date]).dt.year // unit_years * unit_years
)
# 'years'列のデータ型を文字列に変更
df_new["years"] = df_new["years"].astype(str)
return df_new
Show code cell content
def resample_df_by_col_and_years(df: pd.DataFrame, col: str) -> pd.DataFrame:
"""
指定されたカラムと年数に基づき、データフレームを再サンプル
colとyearsの全ての組み合わせが存在するように0埋めを行う
この処理は、作図時にX軸方向の順序が変わることを防ぐために必要
Parameters
----------
df : pd.DataFrame
入力データフレーム
col : str
サンプリング対象のカラム名
Returns
-------
pd.DataFrame
再サンプルされたデータフレーム
"""
# 入力データフレームを新しい変数にコピー
df_new = df.copy()
# データフレームからユニークな年数一覧を取得
unique_years = df["years"].unique()
# データフレームからユニークなcolの値一覧を取得
unique_vals = df[col].unique()
# 一意なカラムの値と年数の全ての組み合わせに対して処理
for val, years in itertools.product(unique_vals, unique_years):
# 対象のカラムの値と年数が一致するデータを抽出
df_tmp = df_new[(df_new[col] == val) & (df_new["years"] == years)]
# 該当するデータが存在しない場合
if df_tmp.shape[0] == 0:
# 0埋めのデータを作成
default_data = {c: 0 for c in df_tmp.columns}
# col列についてはvalで埋める
default_data[col] = val
# years列についてはyearで埋める
default_data["years"] = years
# 新たなレコードとして追加
df_add = pd.DataFrame(default_data, index=[0])
# 0埋めのデータをデータフレームに追加
df_new = pd.concat([df_new, df_add], ignore_index=True)
return df_new
Show code cell content
def save_df_to_csv(df: pd.DataFrame, dir_save: Path, fn_save: str) -> None:
"""
DataFrameをCSVファイルとして指定されたディレクトリに保存する関数
Parameters
----------
df : pd.DataFrame
保存対象となるDataFrame
dir_save : Path
出力先ディレクトリのパス
fn_save : str
保存するCSVファイルの名前(拡張子は含めない)
"""
# 出力先ディレクトリが存在しない場合は作成
dir_save.mkdir(parents=True, exist_ok=True)
# 出力先のパスを作成
p_save = dir_save / f"{fn_save}.csv"
# DataFrameをCSVファイルとして保存する
df.to_csv(p_save, index=False, encoding="utf-8-sig")
# 保存完了のメッセージを表示する
print(f"DataFrame is saved as '{p_save}'.")
可視化例#
まず、可視化対象となるデータを読み込みましょう。
Show code cell content
# pandasのread_csv関数でCSVファイルの読み込み
df_ce = pd.read_csv(DIR_IN / FN_CE)
Show code cell content
# date列をdatetime型に変換
df_ce["date"] = pd.to_datetime(df_ce["date"])
棒グラフ#
本書で扱うマンガ作品の中で最も話数が多い作品はどれでしょうか?
筆者の知る限り、最も長寿な作品はこちら葛飾区亀有公園前派出所です。
本項では、棒グラフを用いて、「本書で扱うマンガ作品の中で最も各話数が多いものはこちら葛飾区亀有公園前派出所である」という仮説を検証します。
棒グラフ ( Bar Chart ) とは、主に質的変数と対応する数量を 棒の長さ で表す可視化手法です。 棒を縦方向に並べることもありますし、横方向に並べることもあります。 質的変数の量を見る最も一般的な方法の一つです。 詳細は5章を参照ください。
Show code cell content
# マンガ作品ごとの掲載話数を集計するためのDataFrameを作成
# 'groupby' と 'nunique' を使用して、各マンガ作品ごとにユニークな 'ceid'(掲載話数)を数える
df_bar = df_ce.groupby(["ccname"])["ceid"].nunique().reset_index(name="n_ce")
# 掲載話数が多い順にデータを並び替えて、上位N_CC件のデータを選択
# 'sort_values' でソートし、'head(20)' で上位20件を取得
df_bar = df_bar.sort_values(by="n_ce", ascending=False, ignore_index=True).head(20)
# 可視化のために'rename'メソッドを用いて列名をわかりやすい名前に変更
df_bar = df_bar.rename(columns={"ccname": "マンガ作品名", "n_ce": "合計話数"})
Show code cell content
# 可視化対象のDataFrameの内容を確認
df_bar.head()
| マンガ作品名 | 合計話数 | |
|---|---|---|
| 0 | こちら葛飾区亀有公園前派出所 | 1968 |
| 1 | はじめの一歩 | 1186 |
| 2 | 名探偵コナン | 1009 |
| 3 | ONE PIECE | 893 |
| 4 | MAJOR | 748 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_bar, DIR_OUT, "bar")
DataFrame is saved as '../../data/cm/output/02/amounts/bar.csv'.
Show code cell source
# 'px.bar' を使用して棒グラフを作成
# x軸には '合計話数'、y軸には 'マンガ作品名' を設定
# 'orientation' を 'h' に設定することで、棒グラフを水平(横向き)に表示
# 'height' でグラフの高さを指定
fig = px.bar(df_bar, x="合計話数", y="マンガ作品名", orientation="h", height=500)
# 作成した棒グラフを表示
show_fig(fig)
上図は、マンガ作品ごとの合計話数を表現した棒グラフです。
こちら葛飾区亀有公園前派出所に代表されるように作品名が長い作品が存在するため、横方向に棒を伸ばした 横棒グラフ を採用しました。
棒グラフを見る限り、こちら葛飾区亀有公園前派出所ほど各話数が多いマンガ作品は存在しないように見えます。
「本書で扱うマンガ作品の中で最も各話数が多いものはこちら葛飾区亀有公園前派出所である」という仮説と整合性のある結果が得られました。
こちら葛飾区亀有公園前派出所はアンケート至上主義の週刊少年ジャンプにて1976年6月から2016年まで 一度の休載もなく続いた 驚異的な作品[修治, 2020]です。
ccidではなくccnameで集計する理由
通常このような集計を行う際、ccidのようなIDを用いることも考えられます。
しかし、基礎分析の結果、ccidとccnameは1対1で対応しておらず(厳密にはn対1で対応しており)、ccidで集計するとMAJORのような大ヒット作品を分割してしまうことがわかっています。
非常に悩みましたが、
今回の分析用途であれば、集計ミスがあってもドメイン知識で検出できる
MAJORのような大ヒット作品を適切に扱えないのは許容できない
という二点の理由からccnameで集計することにしました。
このように、状況に応じて、集計方法を柔軟に変更することがあります。
以降の分析でも、特に断らずにccnameで集計することがありますので、ご了承ください。
集合棒グラフ#
繰り返しになりますが、こちら葛飾区亀有公園前派出所の驚異的な点の一つは、40年以上の連載期間中一度も休載しなかった点です。
そこで本項では、集合棒グラフを用いて「本書で扱うマンガ作品の中で、こちら葛飾区亀有公園前派出所ほど長期間コンスタントに連載したものはない」という仮説を検証します。
集合棒グラフ ( Grouped Bar Chart ) とは、新たな質的変数に応じて棒グラフの内訳を分割し、 並列に並べた 可視化手法です。 詳細は5章を参照ください。
ここでは、年代 ごとに分割することで仮説を確かめましょう。
Show code cell content
# 10年区切りの年代情報を追加
df_ce = add_years_to_df(df_ce)
Show code cell content
# 「ccname」と「years」列で集計し、ユニークな「ceid」の数を「n_ce」列に格納
df_gbar = df_ce.groupby(["ccname", "years"])["ceid"].nunique().reset_index(name="n_ce")
# df_barに含まれるから「マンガ作品名」の上位10作品をリストに格納
ccnames = list(df_bar["マンガ作品名"])[:10]
# df_gbarをフィルタリングして、ccnamesに含まれるccnameのみを選択
df_gbar = df_gbar[df_gbar["ccname"].isin(ccnames)].reset_index(drop=True)
# 「ccname」列と「years」列を使ってdf_gbarをリサンプリング
df_gbar = resample_df_by_col_and_years(df_gbar, "ccname")
# 「ccname」列をカテゴリカルデータとして扱い、ccnamesの順序で並べ替え
df_gbar["ccname"] = pd.Categorical(df_gbar["ccname"], categories=ccnames, ordered=True)
# 「ccname」と「years」でソートし、インデックスをリセット
df_gbar = df_gbar.sort_values(["ccname", "years"], ignore_index=True)
# 可視化用に列名をわかりやすい日本語の名前に変更
df_gbar = df_gbar.rename(
columns={"ccname": "マンガ作品名", "years": "年代", "n_ce": "合計話数"}
)
Show code cell content
# 可視化対象のDataFrameの内容を確認
df_gbar.head()
| マンガ作品名 | 年代 | 合計話数 | |
|---|---|---|---|
| 0 | こちら葛飾区亀有公園前派出所 | 1970 | 160 |
| 1 | こちら葛飾区亀有公園前派出所 | 1980 | 501 |
| 2 | こちら葛飾区亀有公園前派出所 | 1990 | 482 |
| 3 | こちら葛飾区亀有公園前派出所 | 2000 | 492 |
| 4 | こちら葛飾区亀有公園前派出所 | 2010 | 333 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_gbar, DIR_OUT, "gbar")
DataFrame is saved as '../../data/cm/output/02/amounts/gbar.csv'.
Show code cell source
# df_gbarデータを使用して棒グラフを作成
# Y軸にマンガ作品名をとり、X軸に合計話数をとり、色は年代別に表示
# orientationで水平方向の横棒グラフを指定し、barmode=groupで集合棒グラフ化
# グラフの高さは600に調整し、カラーパレットはPortlandを指定
fig = px.bar(
df_gbar,
x="合計話数",
y="マンガ作品名",
color="年代",
orientation="h",
barmode="group",
height=600,
color_discrete_sequence=px.colors.diverging.Portland,
)
# 凡例の位置を図の右上に固定
# yanchorとxanchorは凡例の基準点(top:上部、right:右端)を指定
# yとxはその基準点の位置を指定
fig.update_layout(legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99))
# 集合棒グラフを表示
show_fig(fig)
上図は、年代別のマンガ作品の合計話数を表現した集合棒グラフです。作品名が長いものが多いため、横方向に棒を伸ばした横棒グラフとしました。
こちら葛飾区亀有公園前派出所は1980年代から2000年代まで、全て500話近く掲載していることがわかります。
1970年代および2010年代は、連載開始年と終了年が含まれるため例外的に合計話数が小さくなっていることに注意してください。
他の偉大なマンガ作品と比較しても、複数の年代にわたり500話近くを掲載した作品はありません。
「本書で扱うマンガ作品の中で、こちら葛飾区亀有公園前派出所ほど長期間コンスタントに連載したものはない」という仮説と整合性のある結果が得られました。
上図を更に観察すると、集合棒グラフには次のような長所があることがわかります:
各作品・各年代の絶対値を比較しやすい
例:
1970年代はダメおやじ、1980年代はこちら葛飾区亀有公園前派出所が代表的
各作品がどの年代に掲載されたか定性的にわかりやすい
例:
ダメおやじ等は1970-1980年代、MAJORは1990-2010年代に掲載された
一方で,次のような短所があることもわかります:
年代の数に比例して凡例の数が増えてしまうため、全体的に棒が細くなり視認性が悪い
年代をまたがった合計掲載週数の比較がしづらい
ちなみに、本書では質的変数の中でも、順序尺度と名義尺度でカラースケールを使い分けます。
具体的には、年代等の順序尺度の場合はpx.colors.diverging.Portlandを、マンガ作者名等の名義尺度の場合は岡部・伊藤カラースケール[Okabe and Ito, 2008]を採用します。
group対象に欠損があるとX軸の順序が自動調整されてしまう
おそらくpx.bar()の仕様ですが,barmode="group"あるいはbarmode="stack"を選択した際にcolorで指定した列に欠損があると、X軸の順序が変わってしまうことを確認しました。
これを回避するため、resample_df_by_cname_and_years(df)で欠測を補完しています。
以降も同様です。
積上げ棒グラフ#
集合棒グラフと対になる可視化手法として、積上げ棒グラフがあります。
本項では、積上げ棒グラフを用いて「本書で扱うマンガ作品の中で、こちら葛飾区亀有公園前派出所ほど長期間コンスタントに連載したものはない」という仮説を再度検証してみましょう。
積上げ棒グラフ ( Stacked Bar Chart ) とは、 新たな質的変数に応じて棒グラフの内訳を分割し、 直列に並べた 可視化手法です。 詳細は5章を参照ください。
ここでも、集合棒グラフと同様、 年代 で棒グラフの各要素を分割します。
Show code cell content
# 比較のため、集合棒グラフと同じDataFrameを利用
df_sbar = df_gbar.copy()
Show code cell content
# 可視化対象のDataFrameの内容を確認
df_sbar.head()
| マンガ作品名 | 年代 | 合計話数 | |
|---|---|---|---|
| 0 | こちら葛飾区亀有公園前派出所 | 1970 | 160 |
| 1 | こちら葛飾区亀有公園前派出所 | 1980 | 501 |
| 2 | こちら葛飾区亀有公園前派出所 | 1990 | 482 |
| 3 | こちら葛飾区亀有公園前派出所 | 2000 | 492 |
| 4 | こちら葛飾区亀有公園前派出所 | 2010 | 333 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_sbar, DIR_OUT, "sbar")
DataFrame is saved as '../../data/cm/output/02/amounts/sbar.csv'.
Show code cell source
# df_gbarデータを使用して棒グラフを作成
# Y軸にマンガ作品名をとり、X軸に合計話数をとり、色は年代別に表示
# orientationで水平方向の横棒グラフを指定し、barmode=stackで積上げ棒グラフ化
# グラフの高さは400に調整し、カラーパレットはPortlandを指定
fig = px.bar(
df_gbar,
x="合計話数",
y="マンガ作品名",
color="年代",
orientation="h",
barmode="stack",
height=400,
color_discrete_sequence=px.colors.diverging.Portland,
)
# 凡例の位置を図の右上に固定
# yanchorとxanchorは凡例の基準点(top:上部、right:右端)を指定
# yとxはその基準点の位置を指定
fig.update_layout(legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99))
# 積上げ棒グラフを表示
show_fig(fig)
上図は、年代別のマンガ作品の合計話数を表現した積上げ棒グラフです。作品名が長いものが多いため、横方向に棒を伸ばした横棒グラフとしました。
こちら葛飾区亀有公園前派出所の合計話数の内訳が、(連載開始・終了を含む両端の二色を除き)均等に分割されていることがわかります。
他のマンガ作品は多かれ少なかれ特定の年代に偏っていますので、こちら葛飾区亀有公園前派出所の異常さが際立ちます。
集合棒グラフとは別の観点からですが、「本書で扱うマンガ作品の中で、こちら葛飾区亀有公園前派出所ほど長期間コンスタントに連載したものはない」という仮説と整合性のある結果が得られました。
ここまでの議論から、積上げ棒グラフの長所として以下があることがわかります:
各作品の年代ごとの 内訳 [4]を比較しやすい
例:
銀魂の合計話数は、2000年代と2010年代で4:6程度の比率になっている
各作品の合計掲載週を比較しやすい
例:
こちら葛飾区亀有公園前派出所の合計話数は、名探偵コナン[5]のそれの倍程度ある
棒を補足する必要がないため、集合棒グラフと比較して小さい領域に効率的に描画できる
例:集合棒グラフでは
height=600で可視化した情報を、積上げ棒グラフではheight=400で十分可視化可能
一方で、積上げ棒グラフには次のような短所があることがわかります:
各作品・各年代の絶対値を比較しづらい
つまり、積上げ棒グラフの特徴は集合棒グラフと表裏一体です。
ヒートマップ#
集合棒グラフや積上げ棒グラフでは、 10年 を最小単位として各話数を集計したため、各マンガ作品の細かい時間粒度の各話数の推移を確認することはできませんでした。 特に集合棒グラフでは、二つの年代に跨る期間に集中的に連載したマンガ作品に関して、実際よりも各話数が少ない印象を与えてしまう可能性があります。
そこで、本項ではヒートマップを用いて、より細かい時間粒度で各話数を可視化します。
検証する仮説は「本書で扱うマンガ作品の中で、こちら葛飾区亀有公園前派出所ほど 年間話数 を長期間高水準に維持したものはない」です。
ヒートマップ ( Heatmap ) とは、主に複数の質的変数の組合せに対して、数量を 色 で表す可視化手法です。 (具体的な数値をアノテーションしない限り)定量的な比較は難しいですが、全体像を俯瞰したい場合は非常に便利です。 また、非常に多くの組合せに対して数量を表現できる、という強みもあります。 詳細は5章を参照ください。
本項ではこの特長を利用して、 1年単位 の各話数を可視化します。
Show code cell content
# 1年区切りの年代情報を追加
df_ce = add_years_to_df(df_ce, unit_years=1)
# df_ceをdate順でソートし、全ccnameを事前に抽出しておく、時間順に並んだヒートマップを作成するための工夫
ccnames = (
df_ce.sort_values(["date", "ccid"], ignore_index=True)["ccname"].unique().tolist()
)
Show code cell content
# 「ccname」と「years」と「mcname」列で集計し、ユニークな「ceid」の数を「n_ce」列に格納
df_hm = (
df_ce.groupby(["mcname", "ccname", "years"])["ceid"]
.nunique()
.reset_index(name="n_ce")
)
# ccnamesのうち、df_barの「マンガ作品名」の上位10作品を抽出
# 直接list(df_bar["マンガ作品名"])[:20]を用いないのはccnamesの順序を維持するため
ccnames = [ccname for ccname in ccnames if ccname in list(df_bar["マンガ作品名"])[:20]]
# df_hmをフィルタリングして、ccnamesに含まれるccnameのみを選択
df_hm = df_hm[df_hm["ccname"].isin(ccnames)].reset_index(drop=True)
# 「ccname」列と「years」列を使ってdf_gbarをリサンプリング
df_hm = resample_df_by_col_and_years(df_hm, "ccname")
# 「ccname」列をカテゴリカルデータとして扱い、ccnamesの順序で並べ替え
df_hm["ccname"] = pd.Categorical(df_hm["ccname"], categories=ccnames, ordered=True)
# 「ccname」と「years」でソートし、インデックスをリセット
df_hm = df_hm.sort_values(["ccname", "years"], ignore_index=True)
# 可視化用に列名をわかりやすい日本語の名前に変更
df_hm = df_hm.rename(
columns={
"mcname": "マンガ雑誌名",
"ccname": "マンガ作品名",
"years": "掲載年",
"n_ce": "合計話数",
}
)
Show code cell content
# 可視化対象のDataFrameの内容を確認
df_hm.head()
| マンガ雑誌名 | マンガ作品名 | 掲載年 | 合計話数 | |
|---|---|---|---|---|
| 0 | 週刊少年サンデー | ダメおやじ | 1970 | 9 |
| 1 | 週刊少年サンデー | ダメおやじ | 1971 | 51 |
| 2 | 週刊少年サンデー | ダメおやじ | 1972 | 51 |
| 3 | 週刊少年サンデー | ダメおやじ | 1973 | 50 |
| 4 | 週刊少年サンデー | ダメおやじ | 1974 | 50 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_hm, DIR_OUT, "hm")
DataFrame is saved as '../../data/cm/output/02/amounts/hm.csv'.
Show code cell source
# df_hmデータを使ってヒートマップを作成
# x軸に掲載年、y軸にマンガ作品名、z軸に合計話数を設定
fig = px.density_heatmap(df_hm, x="掲載年", y="マンガ作品名", z="合計話数", height=600)
# ヒートマップを表示
show_fig(fig)
上図は、マンガ作品ごとの年間合計話数を表現したヒートマップです。色が明るいほど合計話数が多いことを表します。 1年間は約52週 ですので、週刊誌に毎週掲載されたとしても最大で52話となります。 さらに、週刊誌は大型連休のタイミングで合併号を出すことが多いので、現実的には最大でも50話程度となるでしょう。
5章で詳しく触れますが、ヒートマップでは横軸の変化に応じて縦軸をソートすると非常に見やすくなります。 上図では、連載開始が早い順に並び替えることで階段状になり、マンガ作品間の比較を容易にする狙いがあります。
下から3行目のこちら葛飾区亀有公園前派出所を確認しましょう。1977年の連載開始以来、色合いがほとんど変わらないまま2016年の連載終了を迎えていることがわかります。
他のマンガ作品はどれも素晴らしいものですが、ここまで長期間年間話数を維持したものはありません。
つまり、「本書で扱うマンガ作品の中で、こちら葛飾区亀有公園前派出所ほど 年間話数 を長期間高水準に維持したものはない」という仮説と整合性のある結果が得られました。
また、副産物的な発見ではありますが、2009年にこちら葛飾区亀有公園前派出所、NARUTO-ナルト-、BLEACH、そして銀魂で不自然に年間話数が増えていることがわかります。
これらのマンガ作品に共通するのは、全て週刊少年ジャンプの連載作品であるという点です。
そこで、2009年の週刊少年ジャンプのマンガ各話数を、マンガ雑誌巻号別に集計してみましょう。
Show code cell content
# 週刊少年ジャンプの2009年のデータを選択し、雑誌巻号ごとにCEIDのユニーク数をカウント
# グループ化して、新しい列n_ceとして格納
df_mi = (
df_ce[(df_ce["mcname"] == "週刊少年ジャンプ") & (df_ce["years"] == "2009")]
.groupby(["miid", "miname"])["ceid"]
.nunique()
.reset_index(name="n_ce")
)
# n_ceで降順ソートして上位5件を表示
df_mi.sort_values("n_ce", ascending=False).head()
| miid | miname | n_ce | |
|---|---|---|---|
| 47 | M542881 | 週刊少年ジャンプ 2009年 表示号数6 | 44 |
| 0 | M542834 | 週刊少年ジャンプ 2010年 表示号数03 | 43 |
| 18 | M542852 | 週刊少年ジャンプ 2009年 表示号数37 | 37 |
| 43 | M542877 | 週刊少年ジャンプ 2009年 表示号数11 | 26 |
| 17 | M542851 | 週刊少年ジャンプ 2009年 表示号数39 | 24 |
週刊少年ジャンプ 2009年 表示号数6、週刊少年ジャンプ 2010年 表示号数03および週刊少年ジャンプ 2010年 表示号数03において、通常の倍以上のマンガ作品が掲載されていたことがわかりました。
最も各話数の多い週刊少年ジャンプ 2009年 表示号数6を見てみましょう。
Show code cell content
# miidが'M542881'であるレコードを選択し、必要な列のみを抽出
# ccname, cename, page_start, pages, dateを選択
# page_startでソートして、ページの開始順に並べ替え
df_ce[df_ce["miid"] == "M542881"][
["ccname", "cename", "page_start", "pages", "date"]
].sort_values("page_start")
| ccname | cename | page_start | pages | date | |
|---|---|---|---|---|---|
| 39979 | NARUTO-ナルト- | 430:ナルト帰還!! | 9.0 | 33.0 | 2009-01-28 |
| 39980 | ONE PIECE | 第527話 紅蓮地獄 | 43.0 | 19.0 | 2009-01-28 |
| 39981 | トリコ | グルメ 32 ロックドラム!! | 63.0 | 19.0 | 2009-01-28 |
| 39982 | BLEACH | 340. The Antagonizer | 83.0 | 19.0 | 2009-01-28 |
| 39983 | 家庭教師ヒットマンREBORN! | 標的 224 XANXUS VS. Rasiel | 103.0 | 17.0 | 2009-01-28 |
| 39984 | ONE PIECE | 消されたライセンス | 130.0 | 1.0 | 2009-01-28 |
| 39985 | こちら葛飾区亀有公園前派出所 | コタツとみかん | 130.0 | 1.0 | 2009-01-28 |
| 39986 | ぬらりひょんの孫 | 1ゆら 2カナ 3つらら | 131.0 | 1.0 | 2009-01-28 |
| 39988 | BLEACH | 白哉玉 | 132.0 | 1.0 | 2009-01-28 |
| 39987 | 魔人探偵脳噛ネウロ | 弥子の正月 | 132.0 | 1.0 | 2009-01-28 |
| 39989 | アスクレピオス | はねつきっス!! | 133.0 | 1.0 | 2009-01-28 |
| 39990 | To LOVEる -とらぶる- | ダーク・オ・セチー | 134.0 | 1.0 | 2009-01-28 |
| 39991 | アイシールド21 | 一富士二鷹三茄子は最高に縁起のいい初夢です | 134.0 | 1.0 | 2009-01-28 |
| 39992 | マイスター | 蹴り初め | 135.0 | 1.0 | 2009-01-28 |
| 39993 | 銀魂 | 汁粉と善哉 | 136.0 | 1.0 | 2009-01-28 |
| 39994 | SKET DANCE | 今年の目標 | 136.0 | 1.0 | 2009-01-28 |
| 39995 | 黒子のバスケ | 本の虫 | 137.0 | 1.0 | 2009-01-28 |
| 39997 | PSYREN-サイレン- | おねがいごと | 138.0 | 1.0 | 2009-01-28 |
| 39996 | 家庭教師ヒットマンREBORN! | ランボはどこ? | 138.0 | 1.0 | 2009-01-28 |
| 39998 | ぼっけさん | ヒノとサユの初詣 | 139.0 | 1.0 | 2009-01-28 |
| 40000 | トリコ | トリコの正月 | 140.0 | 1.0 | 2009-01-28 |
| 39999 | バクマン。 | 俺達に正月休みはない! | 140.0 | 1.0 | 2009-01-28 |
| 40001 | いぬまるだしっ | お正月の思い出作文 | 141.0 | 1.0 | 2009-01-28 |
| 40002 | ピューと吹く!ジャガー | ぼやき初め | 142.0 | 1.0 | 2009-01-28 |
| 40003 | NARUTO-ナルト- | 10年目の真実 | 142.0 | 1.0 | 2009-01-28 |
| 40004 | SKET DANCE | 第72話 ファッショナブル侍 | 145.0 | 17.0 | 2009-01-28 |
| 40005 | ダブルマメダイチ | NaN | 163.0 | 49.0 | 2009-01-28 |
| 40006 | バクマン。 | 20ページ 未来と階段 | 213.0 | 21.0 | 2009-01-28 |
| 40007 | 銀魂 | 第243訓 何回見てもラピュタはいい | 237.0 | 19.0 | 2009-01-28 |
| 40008 | アイシールド21 | 312th down 新世代へ | 257.0 | 19.0 | 2009-01-28 |
| 40009 | いぬまるだしっ | 第20回 1 「たまこ先生の年賀状」 | 299.0 | 3.0 | 2009-01-28 |
| 40010 | いぬまるだしっ | 第20回 2 「アレに似てるおじさん」 | 302.0 | 4.0 | 2009-01-28 |
| 40011 | いぬまるだしっ | 第20回 3 「善と悪」 | 306.0 | 2.0 | 2009-01-28 |
| 40012 | 黒子のバスケ | 第4Q まともじゃないかもしんないスね | 311.0 | 19.0 | 2009-01-28 |
| 40013 | ぼっけさん | 第3怪 存在の証明 | 331.0 | 27.0 | 2009-01-28 |
| 40014 | マイスター | Play.5 スタープレーヤー | 359.0 | 19.0 | 2009-01-28 |
| 40015 | こちら葛飾区亀有公園前派出所 | 初夢の正月クルーズの巻 | 379.0 | 19.0 | 2009-01-28 |
| 40016 | PSYREN-サイレン- | CALL.53 “痛み” | 399.0 | 19.0 | 2009-01-28 |
| 40017 | ぬらりひょんの孫 | 第41幕 百鬼夜行対八十八鬼夜行 | 419.0 | 19.0 | 2009-01-28 |
| 40018 | 魔人探偵脳噛ネウロ | 第188話 距【きょり】 | 439.0 | 19.0 | 2009-01-28 |
| 40019 | To LOVEる -とらぶる- | トラブル 131 クイーンの反抗 | 459.0 | 19.0 | 2009-01-28 |
| 40020 | アスクレピオス | 第15話 結紮!! | 481.0 | 19.0 | 2009-01-28 |
| 40021 | ピューと吹く!ジャガー | 映画公開記念特別編 ・~いま、吹きにゆきます~~の撮影にいま、ゆきます~ | 511.0 | 1.0 | 2009-01-28 |
| 40022 | ピューと吹く!ジャガー | 週刊!?ハミ通SP 特集:文字で見る映画の裏側 | 512.0 | 2.0 | 2009-01-28 |
130ページから142ページにかけて、1ページあたり2作品ずつ掲載されていたことがわかりました。
発売時期や各話タイトルから察するに、お正月企画の4コマ漫画ではないでしょうか。